---
type: tutorial
title: 搭建你的第一个 Astro 岛屿
description: |-
  教程：搭建你的 Astro 博客 -
  使用一个 Preact 组件随机选择欢迎消息来欢迎访问者
---
import Box from '~/components/tutorial/Box.astro';
import Checklist from '~/components/Checklist.astro';
import Spoiler from '~/components/Spoiler.astro';
import MultipleChoice from '~/components/tutorial/MultipleChoice.astro';
import Option from '~/components/tutorial/Option.astro';
import PreCheck from '~/components/tutorial/PreCheck.astro';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';
import { Steps } from '@astrojs/starlight/components';

使用一个 Preact 组件来随机选择欢迎消息来欢迎访问者！

<PreCheck>
  - 在你的 Astro 项目中添加 Preact
  - 在主页上包含 Astro 岛屿（Preact `.jsx` 组件）
  - 使用 `client:` 指令使岛屿可交互
</PreCheck>

## 在你的 Astro 项目中添加 Preact

<Steps>
1. 如果运行着 Astro 开发服务器，请退出以便使用终端（快捷键：<kbd>Ctrl + C</kbd>）。

2. 通过以下单个命令，为你的 Astro 项目添加使用 Preact 组件的能力：

    <PackageManagerTabs>
      <Fragment slot="npm">
        ```sh
        npx astro add preact
        ```
      </Fragment>
      <Fragment slot="pnpm">
        ```sh
        pnpm astro add preact
        ```
      </Fragment>
      <Fragment slot="yarn">
        ```sh
        yarn astro add preact
        ```
      </Fragment>
    </PackageManagerTabs>

3. 按照命令行提示的说明来确认添加 Preact 到你的项目。
</Steps>

## 添加一个 Preact 欢迎横幅

这个组件将接受一个欢迎消息的数组作为属性，并随机选择其中一条消息作为欢迎消息展示给用户。用户可以点击按钮获取新的随机消息。

<Steps>
1. 在 `src/components/` 目录下创建一个名为 `Greeting.jsx` 的新文件。

    注意 `.jsx` 文件扩展名。这个文件将使用 Preact 编写，而不是 Astro。

2. 将以下代码添加到 `Greeting.jsx` 文件中：

    ```jsx title="src/components/Greeting.jsx"
    import { useState } from 'preact/hooks';

    export default function Greeting({messages}) {

      const randomMessage = () => messages[(Math.floor(Math.random() * messages.length))];
      
      const [greeting, setGreeting] = useState(messages[0]);

      return (
        <div> 
          <h3>{greeting}! Thank you for visiting!</h3>
          <button onClick={() => setGreeting(randomMessage())}>
            New Greeting
          </button>
        </div>
      );
    }
    ```

3. 在你的主页 `index.astro` 中导入并使用这个组件。

    ```astro title="src/pages/index.astro" ins={3,8}
    ---
    import BaseLayout from '../layouts/BaseLayout.astro';
    import Greeting from '../components/Greeting';
    const pageTitle = "首页";
    ---
    <BaseLayout pageTitle={pageTitle}>
      <h2>My awesome blog subtitle</h2>
      <Greeting messages={["Hi", "Hello", "Howdy", "Hey there"]} />
    </BaseLayout>
    ```

    在浏览器中预览：你应该看到一个随机的问候，但按钮不起作用！

4. 添加第二个 `<Greeting />` 组件，并加上 `client:load` 指令。

    ```astro title="src/pages/index.astro" ins={9} "client:load"
    ---
    import BaseLayout from '../layouts/BaseLayout.astro';
    import Greeting from '../components/Greeting';
    const pageTitle = "首页";
    ---
    <BaseLayout pageTitle={pageTitle}>
      <h2>My awesome blog subtitle</h2>
      <Greeting messages={["Hi", "Hello", "Howdy", "Hey there"]} />
      <Greeting client:load messages={["Hej", "Hallo", "Hola", "Habari"]} />
    </BaseLayout>
    ```

  5. 查看你的页面并比较这两个组件。第二个按钮可工作，因为 `client:load` 指令告诉 Astro 在页面加载时将其 JavaScript 发送并重新运行到客户端上，使组件可交互。这称为 **被 Hydrate 的** 组件。
  
  6. 一旦明确区别后，删除未被 Hydrate 的 Greeting 组件。

      ```astro title="src/pages/index.astro" del={8} "client:load"
      ---
      import BaseLayout from '../layouts/BaseLayout.astro';
      import Greeting from '../components/Greeting';
      const pageTitle = "首页";
      ---
      <BaseLayout pageTitle={pageTitle}>
        <h2>My awesome blog subtitle</h2>
        <Greeting messages={["Hi", "Hello", "Howdy", "Hey there"]} />
        <Greeting client:load messages={["Hej", "Hallo", "Hola", "Habari"]} />
      </BaseLayout>
      ```
</Steps>
<Box icon="question-mark">

### 分析代码

还有其他的 `client:` 指令可以探索。每个指令在不同的时间将 JavaScript 发送到客户端。例如，`client:visible` 只会在组件在页面上可见时发送其 JavaScript。

考虑一个带有以下代码的 Astro 组件：

```astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import AstroBanner from '../components/AstroBanner.astro';
import PreactBanner from '../components/PreactBanner';
import SvelteCounter from '../components/SvelteCounter.svelte';
---
<BaseLayout>
  <AstroBanner />
  <PreactBanner />
  <PreactBanner client:load />
  <SvelteCounter />
  <SvelteCounter client:visible />
</BaseLayout>
```

1. 五个组件中哪些将成为**被 Hydrate 的**岛屿，将其 JavaScript 发送到客户端？

    <p>
      <Spoiler>`<PreactBanner client:load />` 和 `<SvelteCounter client:visible />` 将成为被 Hydrate 的岛屿。</Spoiler>
    </p>

2. 两个 `<PreactBanner />` 组件在哪些方面相同？在哪些方面不同？

    <p>
      <Spoiler>**相同**：它们都显示相同的 HTML 元素，并且初始外观相同。**不同**：具有 `client:load` 指令的组件会在页面加载后重新渲染，并且任何交互元素都将起作用。</Spoiler>
    </p>

3. 假设 `SvelteCounter` 组件显示一个数字，并带有一个增加数字的按钮。如果你无法看到网站的代码，只能看到发布后的页面，你如何判断哪个 `<SvelteCounter />` 组件使用了 `client:visible`？

    <p>
      <Spoiler>尝试点击按钮，看看哪个是可交互的。如果它对你的输入有响应，那么它肯定使用了 `client:` 指令。</Spoiler>
    </p>
</Box>

<Box icon="question-mark">

### 检验你的知识

对于以下每个组件，确定将发送到浏览器的内容：

1. `<ReactCounter client:load />`

    <MultipleChoice>
      <Option>
        仅 HTML 和 CSS
      </Option>
      <Option isCorrect>
        HTML、CSS 和 JavaScript
      </Option>
    </MultipleChoice>

2. `<SvelteCard />`

    <MultipleChoice>
      <Option  isCorrect>
        仅 HTML 和 CSS
      </Option>
      <Option>
        HTML、CSS 和 JavaScript
      </Option>
    </MultipleChoice>
</Box>

<Box icon="check-list">

## 任务清单

<Checklist>
- [ ] 我可以安装 Astro 集成。
- [ ] 我可以用它们各自的语言编写 UI 框架组件。
- [ ] 我可以在 UI 框架组件上使用 `client:` 指令进行 hydrate。
</Checklist>
</Box>

### 相关资源

- [Astro 集成指南](/zh-cn/guides/integrations-guide/)
- [在 Astro 中使用 UI 框架组件](/zh-cn/guides/framework-components/#使用框架组件)
- [Astro client 指令参考](/zh-cn/reference/directives-reference/#客户端指令)
